﻿using Elio.Interfaces;
using Io.Elegant;
using Google.Protobuf.WellKnownTypes;
using Grpc.Core;
using System.Diagnostics;
using System.Reflection;
using static Io.Elegant.ElegantApi;
using StreamingCall = Grpc.Core.AsyncDuplexStreamingCall
    <Io.Elegant.Interaction, Io.Elegant.Interaction>;
using TcsAny = System.Threading.Tasks.TaskCompletionSource<Google.Protobuf.WellKnownTypes.Any>;

namespace Elio.Sessions;

public class BaseSession : AbstractSession
{
    const int IdNoTask = -1;

    private readonly List<BaseSession> _sessions = new();
    private readonly List<TcsAny> _tasks = new();

    protected BaseSession? Parent { get; set; }
    protected long Id { get; set; }

    protected override string? SessionName { get; set; }
    protected override IClientStreamWriter<Interaction>? ReqStream { get; set; }
    protected override IAsyncStreamReader<Interaction>? RespStream { get; set; }

    protected event EventHandler? DestroyEvent;

    public IElegantPage? ElegantPage { get; set; }

    protected override async Task OnCreate(long sid = 0, long fid = 0, params Any[] argv)
    {
        InitStream();
        new Thread(async () => await ReceiveMessage()).Start();
        await SendAction("onCreate", sid, fid, argv);
    }

    private void InitStream()
    {
        IServiceProvider services = ElioLibProgram.Services!;
        ElegantApiClient client = services.GetService<ElegantApiClient>()!;

        System.Type[] args = new[] { typeof(Metadata), typeof(DateTime?), typeof(CancellationToken) };
        MethodInfo streamCallMethod = typeof(ElegantApiClient).GetMethod(SessionName!, args)!;

        object?[] parameters = { null, null, default(CancellationToken) };
        StreamingCall streamCall = (StreamingCall)streamCallMethod.Invoke(client, parameters)!;

        ReqStream = streamCall.RequestStream;
        RespStream = streamCall.ResponseStream;
    }

    protected override async Task OnReceive(
        string name, long sessionId, long futureId, params Any[] argv)
    {
        switch (name)
        {
            case "create":
                await Create(sessionId, futureId, argv);
                break;
            case "ready":
                Ready(argv[0].P2Long());
                break;
            case "destroy":
                await Destroy();
                break;
            case "callbackResult":
                CallbackResult(argv);
                break;
            default:
                throw new ArgumentException("Invalid argument: " + name);
        }
    }

    protected override async Task Create(long sid, long fid, params Any[] argv)
    {
        BaseSession session = CreateSession(argv[0].P2String())!;
        session.Parent = this;
        _sessions.Add(session);

        void _onDestroy(object? s, EventArgs e)
        {
            session.Parent = null;
            _sessions.Remove(session);
        }
        session.DestroyEvent += _onDestroy;

        await session.OnCreate(sid, fid, argv[1..]);
    }

    private static BaseSession? CreateSession(string sessionName)
    {
        IServiceProvider services = ElioLibProgram.Services!;

        System.Type _type = sessionName.StartsWith("Media")
            ? System.Type.GetType($"Elio.Sessions.Media.{sessionName}Session")!
            : sessionName.StartsWith("Widget")
                ? System.Type.GetType($"Elio.Sessions.Widgets.{sessionName}Session")!
                : System.Type.GetType($"Elio.Sessions.{sessionName}Session")!;
        return services.GetService(_type) as BaseSession;
    }

    private void Ready(long _id) => Id = _id;

    protected override async Task Destroy()
    {
        await OnDestroy();
        await base.Destroy();
    }

    private void CallbackResult(params Any[] argv)
    {
        int taskId = argv[0].P2Int();
        if (taskId == IdNoTask) return;

        TcsAny? tcs = _tasks.Find(t => t.GetHashCode() == taskId);
        if (tcs is null) return;

        _tasks.Remove(tcs);
        tcs.SetResult(argv[1]);
    }

    public async Task SendAction(string name, long sid = 0, long fid = 0, params Any[] argv)
    {
        // 把每一个object对象转换成为Any对象
        //static Any objectToAny(object arg) => arg switch
        //{
        //    string str => str.ToAny(),
        //    int num => num.ToAny(),
        //    double d => d.ToAny(),
        //    bool b => b.ToAny(),
        //    byte[] bytes => bytes.ToAny(),
        //    _ => throw new Exception("Argv Type Error!"),
        //};

        Interaction req = new() { Name = name, Session = sid, Future = fid, };
        argv?.ToList().ForEach(v => { if (v is not null) req.Argv.Add(v); });
        await Send(req);
    }

    public async Task SendActionUntilCallbackResult(
        TcsAny tcs, string name, long sid = 0, long fid = 0, params Any[] argv)
    {
        _tasks.Add(tcs);
        ArgsAddTaskId(out Any[] _argv, argv, tcs.GetHashCode());
        await SendAction(name, sid, fid, _argv);
    }

    public async Task SendCallback(Interaction req, TcsAny? tcs = null, params Any[] argv)
    {
        int taskId = IdNoTask;
        if (tcs is not null)
        {
            taskId = tcs.GetHashCode();
            _tasks.Add(tcs);
        }
        ArgsAddTaskId(out Any[] _argv, argv, taskId);
        await SendAction("onCallback", req.Session, req.Future, _argv);
    }

    protected override async Task OnDestroy()
    {
        _sessions.ForEach(async session => await session.OnDestroy());
        _sessions.Clear();
        _tasks.Clear();

        try { await SendAction("onDestroy"); }
        catch (Exception ex) { Debug.WriteLine(ex.Message); }

        DestroyEvent?.Invoke(this, EventArgs.Empty);
    }

    private static void ArgsAddTaskId(out Any[] any, Any[] args, int taskId)
    {
        int len = args.Length;
        any = new Any[len + 1];
        any[0] = taskId.ToAny();
        for (int i = 0; i < len; i++)
        {
            any[i + 1] = args[i];
        }
    }

    protected override Task OnError() => throw new NotImplementedException();
}
